﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.ComponentModel;

using P2PLib.Server;
using P2PLib.Common.DataModel;
using P2PLib.Common.Command;

namespace ConsoleServer
{
    public class Server
    {
        private System.Collections.Generic.List<ClientManager> clients;
        private BackgroundWorker bgListener;
        private Socket listenerSocket;
        private IPAddress serverIP;
        private int serverPort;

        public Server(IPAddress IP, int port)
        {
            this.serverIP = IP;
            this.serverPort = port;
            clients = new List<ClientManager>(); 
        }

        public void StartServer()
        {
            bgListener = new BackgroundWorker();
            bgListener.DoWork += new DoWorkEventHandler(bgListener_DoWork);
            bgListener.WorkerSupportsCancellation = true;
            bgListener.RunWorkerAsync();
            Console.WriteLine("Starting Server....");
        }


        void bgListener_DoWork(object sender, DoWorkEventArgs e)
        {
            Console.WriteLine("Start listening {0}:{1}", serverIP.ToString(), serverPort);

            listenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            listenerSocket.Bind(new IPEndPoint(serverIP,serverPort));
            listenerSocket.Listen(100);
            while (true)
            {
                this.CreateNewClientManager(this.listenerSocket.Accept());
            }
        }


        private void CreateNewClientManager(Socket socket)
        {
            ClientManager newClientManager = new ClientManager(socket);


            newClientManager.ClientConnected += new ClientConnectedEventHandler(OnClientConnected);
            newClientManager.ClientDisconnected += new ClientDisconnectedEventHandler(OnClientDisconnected);
            
            this.clients.Add(newClientManager);
            this.UpdateConsole("Connected.", newClientManager.IP, newClientManager.Port);
        }


        public void OnClientDisconnected(object sender, ClientEventArgs e)
        {
            //通知其他客户端该客户端的离开
            
            this.RemoveClient((ClientManager)sender);
            this.BroadcastCommand(new CSClientDisconnectCommand(e.Client));
        }

        public void OnClientConnected(object sender, ClientEventArgs e)
        {
            Console.WriteLine("new client,{0},{1}", e.Client.IP.ToString(), e.Client.Port);

            //通知其他客户端新客户端的加入，将当前其他客户端的信息发送给新客户端
            this.BroadcastCommand(new CSNewClientCommand(e.Client));
            this.SendClientListToClient((ClientManager)sender);
        }


        private void UpdateConsole(string status, IPAddress IP, int port)
        {
            Console.WriteLine("Client {0}{1}{2} has been {3} ( {4}|{5} )", IP.ToString(), ":", port.ToString(), status, DateTime.Now.ToShortDateString(), DateTime.Now.ToLongTimeString());
        }
        

        private void BroadcastCommand(CSCommandBase command)
        {
            Console.WriteLine("Broadcasing command ,type:{0}", command.Type);


            CSCommandType type = command.Type;
           
            if (type.Equals(CSCommandType.NewClient))
            {
                CSNewClientCommand cmd = (CSNewClientCommand)command;
                foreach (ClientManager mngr in this.clients)
                    if (!mngr.IP.ToString().Equals(cmd.Client.IP.ToString()))
                        mngr.SendCommand(cmd);
            }

            else
            {
                CSClientDisconnectCommand cmd = (CSClientDisconnectCommand)command;
                foreach (ClientManager mngr in this.clients)
                    if (!mngr.IP.ToString().Equals(cmd.Client.IP.ToString()))
                        mngr.SendCommand(cmd);
            }
            
        }

        private void SendClientListToClient(ClientManager clientManager)
        {
            Console.WriteLine("Sending client list to client:{0}", clientManager.Client.IP.ToString());

            CSClientInfoCommand command = new CSClientInfoCommand();
            List<ClientInfo> infoList = new List<ClientInfo>();
            foreach (ClientManager cm in clients)
            {
                if (cm.IP != clientManager.IP)
                {
                    infoList.Add(cm.Client);
                }
            }
            command.Clients = infoList;
            command.Size = infoList.Count;
            clientManager.SendCommand(command);
        }

        private bool RemoveClient(ClientManager clientManager)
        {
            
            lock (this)
            {
                int index = this.IndexOfClient(clientManager.IP);
                if (index != -1)
                {
                    this.clients.RemoveAt(index);
                    Console.WriteLine("Removing client :{0}", clientManager.IP.ToString());
                    //Inform all clients that a client had been disconnected.
                    //CSClientDisconnectCommand command = new CSClientDisconnectCommand();
                    //command.Client = clientManager.Client;
                    //this.BroadcastCommand(command);
                    return true;
                }
                return false;
            }
            GC.Collect();
        }

        private int IndexOfClient(IPAddress ip)
        {
            int index = -1;
            foreach (ClientManager cMngr in this.clients)
            {
                index++;
                if (cMngr.IP.ToString().Equals(ip.ToString()))
                    return index;
            }
            return -1;
        }
    }
}
